Skip to content

add ability to close window from user code, add HostWindowHandle#103

Merged
micahrj merged 12 commits intoRustAudio:masterfrom
BillyDM:master
Nov 16, 2021
Merged

add ability to close window from user code, add HostWindowHandle#103
micahrj merged 12 commits intoRustAudio:masterfrom
BillyDM:master

Conversation

@BillyDM
Copy link
Copy Markdown
Contributor

@BillyDM BillyDM commented Nov 3, 2021

I've added a method that allows the user to close the window from their code. This is fairly straightforward.

I've also added two new structs: HostHandle and HostWindowHandle. These are used when opening a window in parented mode. The HostWindowHandle is sent back to the user (which they will use as the UI handle in baseplug), and the HostHandle is stored internally in the baseview window.

When the HostWindowHandle is dropped, then the window is closed automatically Also vice versa, if the HostHandle drops unexpectedly, it will trip a flag in the HostWindowHandle, alerting the host/parent (I'm not sure how useful the latter case is, but it was trivial to add in).

I've tested the parentless version of this on Linux, Windows, and Mac. Also the parentless mode in Mac OS was broken anyway, so I've also made changes to fix that as well.

I have also tested this in a plugin context in Bitwig under Linux and Windows. This seems to fix an issue I was having before with the window not opening again after being closed.

Although I have not tested the plugin context in Mac yet since I can't seem to get baseplug working for some reason.

@BillyDM
Copy link
Copy Markdown
Contributor Author

BillyDM commented Nov 9, 2021

Alright, I've merged the latest commit to my branch. I tested the MacOS code on both unparented and as a plugin, and they both seem to work now (except for manually closing a plugin window from the user's code since I'm not sure how to get that to work. Custom "close window" buttons aren't common in plugins anyway so it's probably not a big deal right now).

I've also renamed the HostWindowHandle struct to ChildWindowHandle (and HostHandle to ParentHandle).

Comment thread src/window.rs Outdated
#[cfg(target_os = "linux")]
use crate::x11 as platform;

pub use platform::ChildWindowHandle;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we rename this to just WindowHandle?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly. I just don't want it to cause confusion with the RawWindowHandle crate.

Comment thread src/window.rs Outdated
pub fn open_as_if_parented<H, B>(options: WindowOpenOptions, build: B) -> RawWindowHandle
pub fn open_as_if_parented<H, B>(
options: WindowOpenOptions, build: B,
) -> (RawWindowHandle, ChildWindowHandle)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is OK for now, but we should probably change this so that ChildWindowHandle just implements HasRawWindowHandle.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that works in the open_as_if_parented case, but that won't work in the open_parented case where we don't always get the RawWindowHandle. I suppose we could add a method to ChildWindowHandle that returns an Option<HasRawWindowHandle>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I think technically there's no reason we can't also provide the RawWindowHandle in the open_parented case. But I think it's fine to leave the API like this for now.

Comment thread src/window.rs Outdated
platform::Window::open_blocking::<H, B>(options, build)
}

pub fn request_close(&mut self) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just call this close(), rather than request_close()? "Request close" makes more sense to me as referring to what happens when the user clicks the X on a window, which just sends an event to the application requesting that the window be closed, whereas when the WindowHandler calls this method on the Window, the window should just be closed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think wrl wanted to eventually have the ability to have the app accept or decline a close request (like pop-up a dialog that says "Do you want to save before you exit?" kind of thing).

Of course there are also occasions where the window must close, like when a DAW requests the window to close.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that ability to accept or decline would be in response to the X button being pressed, which is delivered as a particular kind of event (WM_CLOSE on Windows, windowShouldClose: on macOS, and WM_DELETE_WINDOW on X11). So that would make sense as a new WindowEvent called ShouldClose or RequestClose. That's a different situation from this method, which is the application itself telling the window to close.

Comment thread src/win/window.rs Outdated
handler: Box<dyn WindowHandler>,
scale_policy: WindowScalePolicy,
dw_style: u32,
destroy_msg_id: u32,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be a const at the top of the file; I don't think we need to keep it as a member of WindowState.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a constant because we are registering a unique ID at runtime with.

let destroy_msg_id =
                RegisterWindowMessageA("Baseview::DestroyMsg\0".as_ptr() as LPCSTR);

We could do what winit does and use the lazy_static crate, but I'm not sure how well that will work in a plugin context.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I assumed you were just using a WM_USER event, which is what druid-shell does. It doesn't require any registration so might be simpler, but up to you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay. Where does the WM_USER constant come from?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/window.rs Outdated
#[cfg(target_os = "linux")]
use crate::x11 as platform;

pub use platform::ChildWindowHandle;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, could we wrap the platform ChildWindowHandle up in a struct similarly to Window, so that it's easier to catch discrepancies between the APIs on different platforms? And also with a PhantomData<*mut ()> to ensure it is !Send.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handle is meant to be Send. It's whole purpose is when it is dropped by the parent thread it signals the window thread to close.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will need to exist on a different thread from the window on X11 specifically, but that doesn't mean the handle itself needs to be Send. On all three platforms, there is a single thread on which all plugin API windowing operations happen, including requesting for the plugin to open and close its editor window.

So from the standpoint of Baseview's API, the ChildWindowHandle is created via Window::open_parented() on the windowing thread, it stays on the windowing thread for the lifetime of the editor, and then it gets dropped (or close() is called) on the windowing thread. The fact that specifically on X11 Baseview runs the event loop on a different thread doesn't mean that there is any need for a ChildWindowHandle to be sent between threads at any point.

Another reason to keep ChildWindowHandle !Send is that we want closing the window to be performed synchronously. On X11 we can do that using a synchronous channel or by joining the event loop thread, but on Windows and macOS that has to be done using a windowing API call, and that call should not be performed on a different thread from the window.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay.

Comment thread src/win/window.rs Outdated
}

struct ParentHandle {
child_window_dropped: Arc<AtomicBool>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't need to be an Arc<AtomicBool> on Windows, just an Rc<Cell<bool>>, since everything on Windows happens in a single thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although can the DAW be on a different thread?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows and Mac, in every DAW, opening and closing the editor will happen on the same thread as the window.

Comment thread src/win/window.rs Outdated
_ => {}
_ => {
if msg == destroy_msg_id {
// FIXME: handler should decide whether window stays open or not
Copy link
Copy Markdown
Member

@micahrj micahrj Nov 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether the window is closed via Window::close() or by dropping the ChildWindowHandle, the handler doesn't need to be able to decide whether the window stays open or not, since they're already the one who decided to close it in the first place. The handler should get a choice in the case where the X button is clicked (WM_CLOSE event on Windows), which is a different thing, and explicitly has the semantics of requesting the window to be closed but allowing the application to decide.

Comment thread src/win/window.rs Outdated
}
}

impl Drop for ChildWindowHandle {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a matter of preference, but I personally would like it better if there were an explicit close() method on the ChildWindowHandle, and dropping the handle didn't do anything. This sort of mirrors the semantics of JoinHandle in std, where there's an explicit join() method, and dropping the handle just detaches the thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I suppose so.

Comment thread src/win/window.rs Outdated
}

impl ParentHandle {
pub fn new() -> (Self, ChildWindowHandle) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get rid of the ParentHandle struct and just store the Rc<Cell<bool>> window_closed flag directly in WindowState (and then set it to true in WM_NCDESTROY), and then also just have Window::open directly return a ChildWindowHandle (which open_blocking can just ignore)? That will let us get rid of the awkward dance where the HWND is an option that starts out empty and then later gets set by Window::open.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well on the other platforms the ParentHandle contains two atomic bools. I figured I would just keep it consistent.

@BillyDM BillyDM requested a review from micahrj November 12, 2021 22:10
Copy link
Copy Markdown
Member

@micahrj micahrj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good apart from these two changes!

Comment thread src/window.rs Outdated
#[cfg(target_os = "linux")]
use crate::x11 as platform;

pub use platform::WindowHandle;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably didn't explain this clearly; could you make this a wrapper struct that contains the platform::WindowHandle, similarly to Window, with the PhantomData in the outer struct? That way the public API is defined separately from the individual backends, and if a backend doesn't match it it won't build.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. That makes sense.

Comment thread src/x11/window.rs
impl WindowHandle {
pub fn close(&mut self) {
if let Some(_) = self.raw_window_handle.take() {
self.close_requested.store(true, Ordering::Relaxed);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be changed from just setting an atomic to somehow synchronizing with the window being closed (using a synchronous channel, or by joining on the event loop thread). But I'm OK with merging it this way for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I figured I would just do the simplest solution for now so the issue with closing and reopening windows could be fixed in Linux. I'll add a TODO comment explaining the proper solution.

Comment thread src/win/window.rs Outdated
}
}

pub fn window_was_dropped(&self) -> bool {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be renamed to is_open (with the opposite true/false values)? I think that's a lot more clear/obvious.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@BillyDM BillyDM requested a review from micahrj November 15, 2021 15:50
@micahrj micahrj merged commit f6e99e9 into RustAudio:master Nov 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants